/* pipe_sync.c

   Show how pipes can be used for synchronizing the actions of a parent and
   multiple child processes.

   Usage: pipe_sync sleep-time...

   After creating a pipe, the program creates one child for each command-line
   argument. Each child simulates doing some work by sleeping for the number of
   seconds specified in the corresponding command-line argument. When it has
   finished doing its "work", each child closes its file descriptor for the
   write end of the pipe; the parent can see that all children have finished
   their work when it sees end-of-file on the read end of the pipe.
*/

/* Return a string containing the current time formatted according to
   the specification in 'format' (see strftime(3) for specifiers).
   If 'format' is NULL, we use "%c" as a specifier (which gives the'
   date and time as for ctime(3), but without the trailing newline).
   Returns NULL on error.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#define BUF_SIZE 1000

static void errExit(char *msg) {
  perror(msg);
  exit(EXIT_FAILURE);
}

static char *currTime(const char *format) {
  static char buf[BUF_SIZE]; /* Nonreentrant */
  time_t t;
  size_t s;
  struct tm *tm;

  t = time(NULL);
  tm = localtime(&t);
  if (tm == NULL) return NULL;

  s = strftime(buf, BUF_SIZE, (format != NULL) ? format : "%c", tm);

  return (s == 0) ? NULL : buf;
}

int main(int argc, char *argv[]) {
  int pfd[2]; /* Process synchronization pipe */
  int j, dummy;

  if (argc < 2 || strcmp(argv[1], "--help") == 0) {
    fprintf(stderr, "%s sleep-time...\n", argv[0]);
    exit(EXIT_FAILURE);
  }

  setbuf(stdout, NULL); /* Make stdout unbuffered, since we
                           terminate child with _exit() */

  printf("%s  Parent started\n", currTime("%T"));

  if (pipe(pfd) == -1) {
    errExit("pipe");
  }

  for (j = 1; j < argc; j++) {
    switch (fork()) {
      case -1:
        fprintf(stderr, "fork %d", j);
        exit(EXIT_FAILURE);
      case 0:                      /* Child */
        if (close(pfd[0]) == -1) { /* Read end is unused */
          errExit("close");
        }

        /* Child does some work, and lets parent know it's done */
        /* Simulate processing */
        int sleepSecs = strtol(argv[1], NULL, 10);
        sleep(sleepSecs);

        printf("%s  Child %d (PID=%ld) closing pipe\n", currTime("%T"), j,
               (long)getpid());
        if (close(pfd[1]) == -1) {
          errExit("close");
        }

        /* Child now carries on to do other things... */
        /* Simulate processing */
        sleep(sleepSecs);

        printf("%s  Child %d (PID=%ld) exit\n", currTime("%T"), j,
               (long)getpid());
        _exit(EXIT_SUCCESS);

      default: /* Parent loops to create next child */
        break;
    }
  }

  /* Parent comes here; close write end of pipe so we can see EOF */
  if (close(pfd[1]) == -1) { /* Write end is unused */
    errExit("close");
  }

  /* Parent may do other work, then synchronizes with children */
  if (read(pfd[0], &dummy, 1) != 0) {
    fprintf(stderr, "parent didn't get EOF");
    exit(EXIT_FAILURE);
  }

  printf("%s  Parent ready to go\n", currTime("%T"));
  /* Parent can now carry on to do other things... */

  exit(EXIT_SUCCESS);
}